<?php
/**
 * a class used to access ldap service
 * @author tom.wang<tom.wang.sz@qq.com>
 */
class LdapUtil {
	private $cfg;
	private $link;
	
	public function __construct($cfg = array()) {
		$this->setCfg($cfg);
		$this->connect();
	}
	
	/**
	 * search ldap tree
	 * 
	 * @param array $attributes : 让搜索结果返回哪些属性，使用该参数会提高搜索速度
	 * @param integer $attrsonly : 只返回属性名（1）或属性名和属性值都返回（0）
	 * @param integer $sizelimit : 控制返回的记录数，如果server的配置文件已经设置了该值，则这里设再大也没用
	 * @param integer $timelimit : 控制查询时间，如果server的配置文件已经设置了该值，则这里设再大也没用
	 */
	public function search($filter, $attributes = array('*'), $attrsonly = 0, $sizelimit = 0, $timelimit = 0, $deref = LDAP_DEREF_NEVER) {
		//$filter = $this->escape($filter);
		$basedn = $this->getCfg('basedn');
		$result = @ldap_search($this->link, $basedn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
		if(FALSE === $result) {
			FException::throwEx('ldap search fail!');
		}
		
		$info = @ldap_get_entries($this->link, $result);
		if(FALSE === $info) {
			FException::throwEx('ldap search fail!');
		}
		@ldap_free_result($result);
		return $info;
	}
	
	/**
	 * search ldap tree(escape filter)
	 * 
	 * @param array $attributes : 让搜索结果返回哪些属性，使用该参数会提高搜索速度
	 * @param integer $attrsonly : 只返回属性名（1）或属性名和属性值都返回（0）
	 * @param integer $sizelimit : 控制返回的记录数，如果server的配置文件已经设置了该值，则这里设再大也没用
	 * @param integer $timelimit : 控制查询时间，如果server的配置文件已经设置了该值，则这里设再大也没用
	 */
	public function userSearch($filter, $attributes = array('*'), $attrsonly = 0, $sizelimit = 0, $timelimit = 0, $deref = LDAP_DEREF_NEVER) {
		$filter = $this->escape($filter);
		$info = $this->search($filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
		return $info;
	}
	
	/**
	 * add a new entry
	 */
	public function add($dn, $entry = array()) {
		# $entry是属性数组，由于一个属性可以有多个值，所以格式可以是array('attr1' => 'val1', 'attr2' => array('val2', 'val3', 'val4'),)
		if(!@ldap_add($this->link, $dn, $entry)) {
			FException::throwEx('ldap add fail!');
		}
	}
	
	/**
	 * modify an entry
	 */
	public function modify($dn, $entry = array()) {
		if(!@ldap_modify($this->link, $dn, $entry)) {
			FException::throwEx('ldap modify fail!');
		}
	}
	
	/**
	 * remove an entry
	 */
	public function remove($dn) {
		if(!@ldap_delete($this->link, $dn)) {
			FException::throwEx('ldap remove fail!');
		}
	}
	
	/**
	 * close / unbind a ldap connection
	 */
	public function unbind() {
		@ldap_unbind($this->link);
	}
	
	public function close() {
		$this->unbind();
	}
	
	private function connect() {
		$host = $this->getCfg('host');
		$port = $this->getCfg('port');
		$ssl = $this->getCfg('ssl');
		if($ssl) {
			$host = "ldaps://{$host}/";
			$port = $this->getCfg('sslPort');
		}
		$this->link = @ldap_connect($host, $port);
		# openldap2.x.x总是返回资源类型
		if(FALSE === $this->link) {
			FException::throwEx('ldap connect fail!');
		}
		# php手册的ldap_bind页有注释提到将版本号设为3，不然有时候会出错
		@ldap_set_option($this->link, LDAP_OPT_PROTOCOL_VERSION, 3);
		$userdn = $this->getCfg('userdn');
		$userpwd = $this->getCfg('userpwd');
		if(!@ldap_bind($this->link, $userdn, $userpwd)) {
			FException::throwEx('ldap bind fail!');
		}
	}
	
	private function getCfg($item = null) {
		$result = $this->cfg;
		if(null !== $item && array_key_exists($item, $this->cfg)) {
			$result = $this->cfg[$item];
		} else {
			$result = null;
		}
		
		return $result;
	}
	
	private function setCfg($cfg = array()) {
		$this->cfg = require FRAMEWORK_LDAP_ROOT . 'config.php';
		foreach($cfg as $key => $val) {
			if(array_key_exists($key, $this->cfg)) $this->cfg[$key] = $val;
		}
	}
	
	private function escape($str, $for_dn = false) {
		// see:
		// RFC2254
		// http://msdn.microsoft.com/en-us/library/ms675768(VS.85).aspx
		// http://www-03.ibm.com/systems/i/software/ldap/underdn.html       
		   
		if  ($for_dn)
			$metaChars = array(',','=', '+', '<','>',';', '\\', '"', '#');
		else
			$metaChars = array('*', '(', ')', '\\', chr(0));
	
		$quotedMetaChars = array();
		foreach ($metaChars as $key => $value) $quotedMetaChars[$key] = '\\'.str_pad(dechex(ord($value)), 2, '0');
		$str=str_replace($metaChars,$quotedMetaChars,$str); //replace them
		return $str;
	} 
}
?>